package org.yu.api.register;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.particle.ParticleEngine;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.data.recipes.FinishedRecipe;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent;
import net.minecraftforge.client.event.ParticleFactoryRegisterEvent;
import net.minecraftforge.client.model.generators.BlockStateProvider;
import net.minecraftforge.client.model.generators.ItemModelProvider;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.extensions.IForgeMenuType;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.EntityAttributeCreationEvent;
import net.minecraftforge.event.world.BiomeLoadingEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.forge.event.lifecycle.GatherDataEvent;
import net.minecraftforge.network.IContainerFactory;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import org.yu.api.register.data.*;


import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.*;

@SuppressWarnings({"unused"})
public class LazyRegistry {
    protected final String modid;
    protected final CreativeModeTab creativeModeTab;
    protected final DeferredRegister<Item> items;
    protected final DeferredRegister<Block> blocks;
    protected final DeferredRegister<BlockEntityType<?>> blockEntityTypes;
    protected final DeferredRegister<Fluid> fluids;
    protected final DeferredRegister<EntityType<?>> entityTypes;
    protected final DeferredRegister<MenuType<?>> menuTypes;
    protected final DeferredRegister<ParticleType<?>> particleTypes;
    protected final Map<BlockHolder<?>, BiConsumer<BlockStateProvider, BlockHolder<?>>> iBlockStateHashMap = new HashMap<>();
    protected final Map<ItemHolder<?>, BiConsumer<ItemModelProvider, ItemHolder<?>>> itemDataMap = new HashMap<>();
    protected final Multimap<String, Supplier<TranslationBean>> langMultiMap = HashMultimap.create();
    protected final LazyBlockLoot lazyBlockLoot = new LazyBlockLoot();
    protected final List<Pair<Supplier<Consumer<BiConsumer<ResourceLocation, LootTable.Builder>>>, LootContextParamSet>> lootTables = List.of(new Pair<>(() -> lazyBlockLoot, LootContextParamSets.BLOCK));
    protected final Multimap<BlockHolder<?>, TagKey<Block>> blockTagMap = HashMultimap.create();
    protected final Multimap<ItemHolder<?>, TagKey<Item>> itemTagMap = HashMultimap.create();
    protected final HashMap<TagKey<Item>,TagKey<Item>> itemCopyMap=new HashMap<>();
    protected final HashMap<TagKey<Block>,TagKey<Block>> blockCopyMap=new HashMap<>();
    protected final String defaultLang = "en_us";
    protected final List<Runnable> runWhenCommonSetup=new LinkedList<>();
    protected final List<Runnable> runWhenClientSetup=new LinkedList<>();
    protected final List<Runnable> runWhenDataStart =new LinkedList<>();
    protected final HashMap<Holder<PlacedFeature>,Predicate<Biome.BiomeCategory>> oreSpawnMap=new HashMap<>();
    protected final Map<String,RecipeHolder<?>> recipeHolders=new HashMap<>();
    protected final List<RecipeSerializer<?>> customRecipes=new LinkedList<>();
    protected final  List<LazyRecipe> recipeDumpLists =new LinkedList<>();
    protected final List<BiConsumer<Consumer<FinishedRecipe>,VanillaRecipeProvider>> finishedRecipeConsumerList=new LinkedList<>();
    protected final Map<Supplier<Supplier<ModelLayerLocation>>,Supplier<Supplier<LayerDefinition>>> entityLayerMap=new HashMap<>();
    protected final Map<Supplier<EntityType<? extends LivingEntity>>,Supplier<AttributeSupplier.Builder>> entityAttributeMap=new HashMap<>();
    protected final List<Runnable> particleFactoryRunnable=new LinkedList<>();
    protected final DeferredRegister<MobEffect> mobEffects;

    public LazyRegistry(String modid, CreativeModeTab creativeModeTab) {
        this.modid = modid;
        this.creativeModeTab = creativeModeTab;
        items = DeferredRegister.create(ForgeRegistries.ITEMS, modid);
        blocks = DeferredRegister.create(ForgeRegistries.BLOCKS, modid);
        blockEntityTypes = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, modid);
        fluids = DeferredRegister.create(ForgeRegistries.FLUIDS, modid);
        entityTypes=DeferredRegister.create(ForgeRegistries.ENTITIES,modid);
        menuTypes = DeferredRegister.create(Registry.MENU.key(), modid);
        particleTypes=DeferredRegister.create(ForgeRegistries.PARTICLE_TYPES,modid);
        mobEffects=DeferredRegister.create(ForgeRegistries.MOB_EFFECTS,modid);
        var modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
        blocks.register(modEventBus);
        items.register(modEventBus);
        blockEntityTypes.register(modEventBus);
        fluids.register(modEventBus);
        menuTypes.register(modEventBus);
        entityTypes.register(modEventBus);
        particleTypes.register(modEventBus);
        mobEffects.register(modEventBus);
        modEventBus.addListener(this::dataSetup);
        modEventBus.addListener(this::commonSetup);
        modEventBus.addListener(this::clientSetup);
        modEventBus.addListener(this::registerLivingEntityAttribute);
        modEventBus.addListener(this::registerEntityLayers);
        if(FMLLoader.getDist()==Dist.CLIENT)
            modEventBus.addListener(this::addParticleFactory);
        modEventBus.addGenericListener(RecipeSerializer.class,this::registerRecipe);
        MinecraftForge.EVENT_BUS.addListener(EventPriority.NORMAL, false, BiomeLoadingEvent.class, this::biomeLoading);
        this.lang(defaultLang,creativeModeTab.getDisplayName().getString(),LangBuilder.toTitleCase(modid));
    }
    public void executeWhenCommonSetup(Runnable runnable){
        runWhenCommonSetup.add(runnable);
    }
    public void executeWhenClientSetup(Runnable runnable){
        runWhenClientSetup.add(runnable);
    }
    public void executeWhenDataStart(Runnable runnable){
        runWhenDataStart.add(runnable);
    }
    private void biomeLoading(BiomeLoadingEvent event) {
        oreSpawnMap.forEach((placedFeature, biomePredicate) -> {
            if(biomePredicate.test(event.getCategory()))
                event.getGeneration().addFeature(GenerationStep.Decoration.UNDERGROUND_ORES,placedFeature);
        });
    }
    private void registerRecipe(RegistryEvent.Register<RecipeSerializer<?>> registryEvent){
        recipeHolders.forEach((name,holder)-> {
            registryEvent.getRegistry().register(holder.serializer);
            holder.recipeType= RecipeType.register(modid+":"+name);
        });
        customRecipes.forEach(recipeSerializer ->  registryEvent.getRegistry().register(recipeSerializer));
    }
    private void  registerLivingEntityAttribute(EntityAttributeCreationEvent event){
        entityAttributeMap.forEach((entityTypeSupplier, builder) -> {
            event.put(entityTypeSupplier.get(),builder.get().build());
        });
    }
    private void registerEntityLayers(EntityRenderersEvent.RegisterLayerDefinitions event) {
        entityLayerMap.forEach((e,l)->event.registerLayerDefinition(e.get().get(),l.get()));
    }
    public  <T extends ParticleOptions>  void registerParticleFactory(Supplier<ParticleType<T>> type, Supplier<ParticleEngine.SpriteParticleRegistration<T>> pParticleMetaFactory){
        particleFactoryRunnable.add(() -> Minecraft.getInstance().particleEngine.register(type.get(),pParticleMetaFactory.get()));
    }
    private void addParticleFactory(ParticleFactoryRegisterEvent event){
        particleFactoryRunnable.forEach(Runnable::run);
    }
    public <T extends LazyRecipe> RecipeHolder<T> recipe(String name,LazyRecipeSerializer<T> recipeSerializer){
        RecipeHolder<T> recipeHolder=new RecipeHolder<>();
        recipeHolder.serializer= recipeSerializer;
        recipeHolder.serializer.setRegistryName(new ResourceLocation(modid,name));
        recipeHolders.put(name,recipeHolder);
        return recipeHolder;
    }
    public <T extends Recipe<?>> RecipeSerializer<T> customRecipe(String name, RecipeSerializer<T> recipeSerializer){
        recipeSerializer.setRegistryName(new ResourceLocation(modid,name));
        customRecipes.add(recipeSerializer);
        return recipeSerializer;
    }
    public <T extends Entity> EntityTypeBuilder<T> entity(String name,EntityType.EntityFactory<T> factory){
        return new EntityTypeBuilder<T>(this,name,factory);
    }
    public <T extends LivingEntity> LivingTypeBuilder<T> livingEntity(String name,EntityType.EntityFactory<T> factory){
        return new LivingTypeBuilder<>(this,name,factory);
    }
    public <T extends Mob> MobEntityTypeBuilder<T> mob(String name, EntityType.EntityFactory<T> factory){
        return new MobEntityTypeBuilder<>(this,name,factory);
    }
    public void runOnClient(Runnable runnable){
        DistExecutor.unsafeRunWhenOn(Dist.CLIENT,()->runnable);
    }
    public  SimpleParticleTypeBuilder particle(String name,boolean alwaysShow){
        return new SimpleParticleTypeBuilder(this,name,alwaysShow);
    }
    public <T extends ParticleType<?>&ParticleOptions> ParticleTypeBuilder<T> particle(String name, Supplier<T> typeSupplier){
        return new ParticleTypeBuilder<>(this, name, typeSupplier);
    }
    public <T extends MobEffect> MobEffectBuilder<T> mobEffect(String name, BiFunction<MobEffectCategory,Integer,T> function){
        return new MobEffectBuilder<>(this,name,function);
    }
    private void dataSetup(GatherDataEvent event) {
        var generator = event.getGenerator();
        var helper = event.getExistingFileHelper();
        runWhenDataStart.forEach(Runnable::run);
        generator.addProvider(new LazyBlockStateProvider(generator, modid, helper, iBlockStateHashMap));
        generator.addProvider(new LazyItemModelProvider(generator, modid, helper, itemDataMap));
        generator.addProvider(new LazyLangProvider(generator, modid, langMultiMap));
        generator.addProvider(new LazyLootProvider(generator, lootTables));
        LazyBlockTagProvider lazyBlockTagProvider = new LazyBlockTagProvider(generator, modid, helper, blockTagMap,blockCopyMap);
        generator.addProvider(lazyBlockTagProvider);
        generator.addProvider(new LazyItemTagProvider(generator, lazyBlockTagProvider, modid, helper, itemTagMap,itemCopyMap));
        generator.addProvider(new LazyRecipeProvider(this,generator,helper, recipeDumpLists));
        generator.addProvider(new VanillaRecipeProvider(generator,finishedRecipeConsumerList));
    }
    private void commonSetup(FMLCommonSetupEvent event) {
        runWhenCommonSetup.forEach(event::enqueueWork);
    }
    private void clientSetup(FMLClientSetupEvent event) {
        runWhenClientSetup.forEach(event::enqueueWork);
    }

    public String getModID() {
        return modid;
    }

    public OreFeatureBuilder orePlaced(String name, BlockHolder<?> oreBlock, int veinPreChunk){
        return new OreFeatureBuilder(this,name,oreBlock,veinPreChunk);
    }

    public <T extends Item> ItemBuilder<T> item(String name, Function<Item.Properties, T> itemFunc) {
        return new ItemBuilder<>(this, name, itemFunc, itemDataMap);
    }
    @SuppressWarnings("all")
    public  LazyRegistry dumpRecipe(String id,LazyRecipe recipe){
        recipe.setId(new ResourceLocation(modid,id));
        recipeDumpLists.add(recipe);
        return this;
    }
    public LazyRegistry dumpVanillaRecipe(BiConsumer<Consumer<FinishedRecipe>, VanillaRecipeProvider> consumerConsumer){
        finishedRecipeConsumerList.add(consumerConsumer);
        return this;
    }
    public ItemBuilder<PickaxeItem> pickaxe(String name, Tier tier) {
        return item(name, properties -> new PickaxeItem(tier, 1, -2.8f, properties.stacksTo(1)));
    }

    public ItemBuilder<ShovelItem> shovel(String name, Tier tier) {
        return item(name, properties -> new ShovelItem(tier, 1.5f, -3.0f, properties.stacksTo(1)));
    }

    public ItemBuilder<AxeItem> axe(String name, Tier tier) {
        return item(name, properties -> new AxeItem(tier, 5.0f, -3.0f, properties.stacksTo(1)));
    }

    public ItemBuilder<SwordItem> sword(String name, Tier tier) {
        return item(name, properties -> new SwordItem(tier, 3, -2.4f, properties.stacksTo(1)));
    }

    public ItemBuilder<HoeItem> hoe(String name, Tier tier) {
        return item(name, properties -> new HoeItem(tier, -3, 0, properties.stacksTo(1)));
    }

    public void lang(String lang, String key, String value) {
        this.lang(lang, () -> new TranslationBean(key, value));
    }

    public void lang(String lang, Supplier<TranslationBean> translateBean) {
        langMultiMap.put(lang, translateBean);
    }

    public LazyRegistry tagBlock(TagKey<Block> tagKey, BlockHolder<?> blockHolder) {
        blockTagMap.put(blockHolder, tagKey);
        return this;
    }

    public LazyRegistry tagItem(TagKey<Item> tagKey, ItemHolder<?> itemHolder) {
        itemTagMap.put(itemHolder, tagKey);
        return this;
    }
    public LazyRegistry copyItemTag(TagKey<Item> from,TagKey<Item> to){
        itemCopyMap.put(from,to);
        return this;
    }
    public LazyRegistry copyBlockTag(TagKey<Block> from,TagKey<Block> to){
        blockCopyMap.put(from,to);
        return this;
    }
    public <T extends AbstractContainerMenu> MenuHolder<T> menu(String name, LazyMenu.LazyMenuType<T> factory) {
        return new MenuTypeBuilder<T>(this, name, factory).build();
    }

    public <T extends AbstractContainerMenu> RegistryObject<MenuType<T>> menu2(String name, IContainerFactory<T> factory) {
        return menuTypes.register(name, () -> IForgeMenuType.create(factory));
    }

    /**
     * 构建翻译文件
     *
     * @return LangBuilder
     */
    public LangBuilder lang() {
        return new LangBuilder(this);
    }

    /**
     * 创建方块
     *
     * @param name      名字
     * @param blockFunc 方块构造器
     * @param <T>       方块类型
     * @return BlockBuilder
     */
    public <T extends Block> BlockBuilder<T> block(String name, Function<BlockBehaviour.Properties, T> blockFunc) {
        return new BlockBuilder<>(this, name, blockFunc, iBlockStateHashMap);
    }

    public <T extends BlockEntity> BlockEntityTypeBuilder<T> blockEntity(String name, BlockEntityTypeHolder.BESupplier<T> supplier) {
        return new BlockEntityTypeBuilder<>(this, name, supplier);
    }

    public FluidBuilder fluid(String name) {
        return new FluidBuilder(this, name);
    }

}
